package com.msb.mall.producet.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.msb.mall.producet.service.CategoryBrandRelationService;
import com.msb.mall.producet.vo.Catalog2VO;
import org.apache.commons.lang.StringUtils;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.msb.common.utils.PageUtils;
import com.msb.common.utils.Query;

import com.msb.mall.producet.dao.CategoryDao;
import com.msb.mall.producet.entity.CategoryEntity;
import com.msb.mall.producet.service.CategoryService;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;


@Service("categoryService")
public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity> implements CategoryService {

    @Autowired
    CategoryBrandRelationService categoryBrandRelationService;

    @Autowired
    StringRedisTemplate stringRedisTemplate;


    @Autowired
    RedissonClient redissonClient;

    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        IPage<CategoryEntity> page = this.page(
                new Query<CategoryEntity>().getPage(params),
                new QueryWrapper<CategoryEntity>()
        );

        return new PageUtils(page);
    }

    /**
     * 查询
     *
     * @param params
     * @return
     */
    @Override
    public List<CategoryEntity> queryPageWithTree(Map<String, Object> params) {
        //查询所有的商品分类信息
        List<CategoryEntity> categoryEntities = baseMapper.selectList(null);

        Map<Long, List<CategoryEntity>> parentId2List = categoryEntities.stream().collect(Collectors.groupingBy(e -> e.getParentCid()));


        //将商品分类转为树形结构  --递归
        List<CategoryEntity> result = categoryEntities.stream().filter(e -> e.getParentCid() == 0).map(e -> {
            //根据大类找到小类

            getCateGoryChildrens(e, parentId2List);
            return e;
        }).sorted((c1, c2) -> (c1.getSort() == null ? 0 : c1.getSort()) - (c2.getSort() == null ? 0 : c2.getSort())).collect(Collectors.toList());


        return result;
    }


    private void getCateGoryChildrens(CategoryEntity e, Map<Long, List<CategoryEntity>> map) {
        List<CategoryEntity> children = map.get(e.getCatId());
        if (children == null || children.isEmpty()) {
            return;
        }
        List<CategoryEntity> _children = children.stream().sorted((c1, c2) -> (c1.getSort() == null ? 0 : c1.getSort()) - (c2.getSort() == null ? 0 : c2.getSort())).collect(Collectors.toList());
        e.setChildren(_children);
        for (CategoryEntity child : children) {
            getCateGoryChildrens(child, map);
        }
    }

    @Override
    public void removeCategoryByIds(List<Long> ids) {
        // todo 1、检查类别数据是否在其他业务中使用到

        //批量逻辑删除
        baseMapper.deleteBatchIds(ids);

    }

    @Override
    public Long[] findCatelogPath(Long catelogId) {
        List<Long> paths = new ArrayList<>();
        findParentPath(catelogId, paths);
        Collections.reverse(paths);
        return paths.toArray(new Long[paths.size()]);
    }


//    @CacheEvict(value = "catagory",key = "'getLevel1Category'")   //更新数据的时候同步更新缓存中的数据
    @Caching(evict = {
            @CacheEvict(value = "catagory",key = "'getLevel1Category'"),
            @CacheEvict(value = "catagory",key = "'getCatalog2JSON'")
    })  //更新数据时  同时删除这两个key对应的缓存    --组合注解
    @CacheEvict(value = "catagory", allEntries = true)   //删除这个分区下的所有缓存
    @Transactional
    @Override
    public void updateDetail(CategoryEntity category) {
        this.updateById(category);
        if (StringUtils.isNotEmpty(category.getName())) {
            //同步更新级联数据
            categoryBrandRelationService.updateCatelogName(category.getCatId(), category.getName());

            //todo 同步更新其他的冗余数据
        }
    }


    /**
     * 查询出所有的商品大类 (一级分类)
     *
     * @return
     */
    @Override
    @Cacheable(value = {"catagory"}, key = "#root.method.name")    //缓存有数据就从缓存获取   缓存没有   则获取数据后  放到缓存
    public List<CategoryEntity>  getLevel1Category() {
        List<CategoryEntity> list = baseMapper.queryLevel1Category();
        return list;
    }


    /**
     * 根据 父编号获取对应的子菜单信息
     *
     * @param list
     * @param parentCid
     * @return
     */
    private List<CategoryEntity> queryByParentCid(List<CategoryEntity> list, Long parentCid) {
        List<CategoryEntity> collect = list.stream().filter(item -> {
            return item.getParentCid().equals(parentCid);
        }).collect(Collectors.toList());
        return collect;
    }


    /**
     * 查询二三级分类数据    注解版
     * @return
     */
    @Cacheable(value = "catagory", key = "#root.methodName")
    @Override
    public Map<String, List<Catalog2VO>> getCatalog2JSON() {
        //查询所有的分类数据
        List<CategoryEntity> all = baseMapper.selectList(new QueryWrapper<CategoryEntity>());

        //获取所有的一级分类的数据
        List<CategoryEntity> level1Category = this.queryByParentCid(all, 0L);

        //找到一级分类对应的二级分类
        //把一级分类的数据转成map    key:一级分类的编号    value: 对应的二级分类的数据
        Map<String, List<Catalog2VO>> map = level1Category.stream().collect(Collectors.toMap(key -> key.getCatId().toString(), value -> {

            //根据一级分类的编号查询出对应二级分类的数据
            List<CategoryEntity> l2Catagorys = this.queryByParentCid(all, value.getCatId());
            List<Catalog2VO> catalog2VOs = null;
            if (!CollectionUtils.isEmpty(l2Catagorys)) {
                catalog2VOs = l2Catagorys.stream().map(l2 -> {
                    Catalog2VO catalog2VO = new Catalog2VO(l2.getParentCid().toString(), null, l2.getCatId().toString(), l2.getName());

                    //根据二级分类编号找到对应的三级分类的数据
                    List<CategoryEntity> l3Catagorys = this.queryByParentCid(all, l2.getCatId());
                    if (!CollectionUtils.isEmpty(l3Catagorys)) {
                        //获取二级分类对应的三级分类数据
                        List<Catalog2VO.Catalog3VO> catalog3VOS = l3Catagorys.stream().map(l3 -> {
                            Catalog2VO.Catalog3VO catalog3VO = new Catalog2VO.Catalog3VO(l3.getParentCid().toString(), l3.getCatId().toString(), l3.getName());
                            return catalog3VO;
                        }).collect(Collectors.toList());

                        //三级分类关联二级分类
                        catalog2VO.setCatalog3List(catalog3VOS);
                    }

                    return catalog2VO;
                }).collect(Collectors.toList());
            }

            return catalog2VOs;
        }));
        return map;


    }

    /**
     * 获取二三级分类数据 走缓存   非注解版
     * @return
     */
    public Map<String, List<Catalog2VO>> getCatalog2JSONWithRedis() {
        String key = "catalogJSON";
        //先从redis拿数据
        String catalogJSON = stringRedisTemplate.opsForValue().get(key);
        if (StringUtils.isEmpty(catalogJSON)) {
            //没有数据  从DB拿
//            Map<String, List<Catalog2VO>> catalog2JSONForDB = getCatalog2JSONForDB();
            //走数据库拿数据        走时候加锁--解决缓存击穿
//            Map<String, List<Catalog2VO>> catalog2JSONForDB = getCatalog2JSONForDBWithRedisLock();
            Map<String, List<Catalog2VO>> catalog2JSONForDB = getCatalog2JSONForDBWithRedisson();
            return catalog2JSONForDB;
        } else {
            System.out.println(11111111);
        }


        Map<String, List<Catalog2VO>> stringListMap = JSON.parseObject(catalogJSON, new TypeReference<Map<String, List<Catalog2VO>>>() {
        });

        return stringListMap;


    }

    /**
     * 查询出所有的二级和三级分类的数据   --本地所
     *
     * @return
     */
    public Map<String, List<Catalog2VO>> getCatalog2JSONForDB() {
        String key1 = "catalogJSON";

        synchronized (this) {
            //多个线程进来后  先去缓存查询   没有再走数据库
            String catalogJSON = stringRedisTemplate.opsForValue().get(key1);
            if (StringUtils.isNotEmpty(catalogJSON)) {
                System.out.println("走数据库查询到了缓存");
                //命中缓存  直接返回
                Map<String, List<Catalog2VO>> stringListMap = JSON.parseObject(catalogJSON, new TypeReference<Map<String, List<Catalog2VO>>>() {
                });
                return stringListMap;
            }


            //查询所有的分类数据
            List<CategoryEntity> all = baseMapper.selectList(new QueryWrapper<CategoryEntity>());

            //获取所有的一级分类的数据
            List<CategoryEntity> level1Category = this.queryByParentCid(all, 0L);

            //找到一级分类对应的二级分类
            //把一级分类的数据转成map    key:一级分类的编号    value: 对应的二级分类的数据
            Map<String, List<Catalog2VO>> map = level1Category.stream().collect(Collectors.toMap(key -> key.getCatId().toString(), value -> {

                //根据一级分类的编号查询出对应二级分类的数据
                List<CategoryEntity> l2Catagorys = this.queryByParentCid(all, value.getCatId());
                List<Catalog2VO> catalog2VOs = null;
                if (!CollectionUtils.isEmpty(l2Catagorys)) {
                    catalog2VOs = l2Catagorys.stream().map(l2 -> {
                        Catalog2VO catalog2VO = new Catalog2VO(l2.getParentCid().toString(), null, l2.getCatId().toString(), l2.getName());

                        //根据二级分类编号找到对应的三级分类的数据
                        List<CategoryEntity> l3Catagorys = this.queryByParentCid(all, l2.getCatId());
                        if (!CollectionUtils.isEmpty(l3Catagorys)) {
                            //获取二级分类对应的三级分类数据
                            List<Catalog2VO.Catalog3VO> catalog3VOS = l3Catagorys.stream().map(l3 -> {
                                Catalog2VO.Catalog3VO catalog3VO = new Catalog2VO.Catalog3VO(l3.getParentCid().toString(), l3.getCatId().toString(), l3.getName());
                                return catalog3VO;
                            }).collect(Collectors.toList());

                            //三级分类关联二级分类
                            catalog2VO.setCatalog3List(catalog3VOS);
                        }

                        return catalog2VO;
                    }).collect(Collectors.toList());
                }

                return catalog2VOs;
            }));
            //防止缓存穿透
            if (map == null) {
                //说明数据库中也不存在
                stringRedisTemplate.opsForValue().set(key1, "1", 5, TimeUnit.SECONDS);
            } else {
                String jsonString = JSON.toJSONString(map);

                //解决缓存雪崩   --设置 随机过期时间
                stringRedisTemplate.opsForValue().set(key1, jsonString, 10, TimeUnit.MINUTES);
            }
            return map;
        }
    }


    /**
     * 加上分布式锁   redis --> setNX
     *
     * @return
     */
    public Map<String, List<Catalog2VO>> getCatalog2JSONForDBWithRedisLock() {
        String key = "catalogJSON";
        //加锁
        //要保证setNX 和expire  要保证原子性
        String uuid = UUID.randomUUID().toString();
        Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", uuid, 30, TimeUnit.SECONDS);
        if (lock) {
            System.out.println("获取分布式锁成功.....");
            Map<String, List<Catalog2VO>> data = null;
            try {
                //加锁成功
                data = getDataFromDB(key);
            } finally {
                //通过redis的lua脚本保证  查询和删除操作的原子性
                String lua = "if redis.call(\"get\",KEYS[1]) == ARGV[1]\n" +
                        "then\n" +
                        "    return redis.call(\"del\",KEYS[1])\n" +
                        "else\n" +
                        "    return 0\n" +
                        "end";
                stringRedisTemplate.execute(new DefaultRedisScript<Long>(lua, Long.class), Arrays.asList("lock"), uuid);
            }

            /*//获取key的值
            String val = stringRedisTemplate.opsForValue().get("lock");

            if (uuid.equals(val)) {
                //获取数据成功之后  应该释放锁 并且只能删除自己设置的锁
                stringRedisTemplate.delete("lock");
            }*/
            return data;
        } else {
            //加锁失败
            //休眠+重试
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("获取锁失败.....");
            return getCatalog2JSONForDBWithRedisLock();
        }
    }


    /**
     * 分布式锁  redisson
     *
     * @return
     */
    public Map<String, List<Catalog2VO>> getCatalog2JSONForDBWithRedisson() {
        String key = "catalogJSON";
        //获取分布式锁对象
        RLock lock = redissonClient.getLock("Catelog2JSON-lock");
        //加锁
        Map<String, List<Catalog2VO>> data = null;
        try {
            lock.lock();
            //加锁成功
            data = getDataFromDB(key);
        } finally {
            lock.unlock();
        }
        return data;
    }


    /**
     * 从数据库中查询数据
     *
     * @param key1
     * @return
     */
    private Map<String, List<Catalog2VO>> getDataFromDB(String key1) {
        System.out.println("进入DB中....");
        //多个线程进来后  先去缓存查询   没有再走数据库
        String catalogJSON = stringRedisTemplate.opsForValue().get(key1);
        if (StringUtils.isNotEmpty(catalogJSON)) {
            System.out.println("走数据库查询到了缓存");
            //命中缓存  直接返回
            Map<String, List<Catalog2VO>> stringListMap = JSON.parseObject(catalogJSON, new TypeReference<Map<String, List<Catalog2VO>>>() {
            });
            return stringListMap;
        }

        System.out.println("数据库中查询数据...");

        //查询所有的分类数据
        List<CategoryEntity> all = baseMapper.selectList(new QueryWrapper<CategoryEntity>());

        //获取所有的一级分类的数据
        List<CategoryEntity> level1Category = this.queryByParentCid(all, 0L);

        //找到一级分类对应的二级分类
        //把一级分类的数据转成map    key:一级分类的编号    value: 对应的二级分类的数据
        Map<String, List<Catalog2VO>> map = level1Category.stream().collect(Collectors.toMap(key -> key.getCatId().toString(), value -> {

            //根据一级分类的编号查询出对应二级分类的数据
            List<CategoryEntity> l2Catagorys = this.queryByParentCid(all, value.getCatId());
            List<Catalog2VO> catalog2VOs = null;
            if (!CollectionUtils.isEmpty(l2Catagorys)) {
                catalog2VOs = l2Catagorys.stream().map(l2 -> {
                    Catalog2VO catalog2VO = new Catalog2VO(l2.getParentCid().toString(), null, l2.getCatId().toString(), l2.getName());

                    //根据二级分类编号找到对应的三级分类的数据
                    List<CategoryEntity> l3Catagorys = this.queryByParentCid(all, l2.getCatId());
                    if (!CollectionUtils.isEmpty(l3Catagorys)) {
                        //获取二级分类对应的三级分类数据
                        List<Catalog2VO.Catalog3VO> catalog3VOS = l3Catagorys.stream().map(l3 -> {
                            Catalog2VO.Catalog3VO catalog3VO = new Catalog2VO.Catalog3VO(l3.getParentCid().toString(), l3.getCatId().toString(), l3.getName());
                            return catalog3VO;
                        }).collect(Collectors.toList());

                        //三级分类关联二级分类
                        catalog2VO.setCatalog3List(catalog3VOS);
                    }

                    return catalog2VO;
                }).collect(Collectors.toList());
            }

            return catalog2VOs;
        }));
        //防止缓存穿透
        if (map == null) {
            //说明数据库中也不存在
            stringRedisTemplate.opsForValue().set(key1, "1", 5, TimeUnit.SECONDS);
        } else {
            String jsonString = JSON.toJSONString(map);

            //解决缓存雪崩   --设置 随机过期时间
            stringRedisTemplate.opsForValue().set(key1, jsonString, 10, TimeUnit.MINUTES);
        }
        return map;
    }

    /**
     * 225.25.2
     *
     * @param catelogId
     * @param paths
     * @return
     */
    private void findParentPath(Long catelogId, List<Long> paths) {
        paths.add(catelogId);
        CategoryEntity entity = this.getById(catelogId);
        if (entity.getParentCid() != 0) {
            findParentPath(entity.getParentCid(), paths);
        }
    }

}